#pragma once

#include "Epoller.hpp"
#include "Connection.hpp"
#include <unordered_map>


using namespace EpollerModule;

#define EPOLL_BLOCK -1
const static int g_max_events = 1024;

class TcpServer
{
public:
    TcpServer(): _is_running(false), _timeout_ms(EPOLL_BLOCK)
    {
        _epoller.init();
    }
    
    bool isConnectionExist(int fd)
    {
        return _connections.find(fd) != _connections.end();
    }

    void addConnection(Connection::ptr conn)
    {
        if(conn == nullptr) { return; }
        if(isConnectionExist(conn->getFd())) { return; }

        // 1. add to connection map
        _connections[conn->getFd()] = conn;
        // 2. add to epoller
        _epoller.addEvent(conn->getFd(), conn->getEvents());
    }

    void removeConnection(int fd)
    {
        if(!isConnectionExist(fd)) { return; }

        // 1. cancel the concern
        // 1) cancel from connection map
        _connections[fd]->setEvents(0);
        // 2) cancel from epoller
        _epoller.delEvent(fd);
        // 2. remove from connection map
        _connections.erase(fd);
    }

    void enableReadWrite(int fd, bool enable_read, bool enable_write)
    {
        if(!isConnectionExist(fd)) { return; }

        uint32_t events = (enable_read? EPOLLIN : 0) | (enable_write? EPOLLOUT : 0) | EPOLLET;
        // 1. modify the connection in the map
        _connections[fd]->setEvents(events);
        // 2. modify the epoller
        _epoller.modEvent(fd, events);
    }

    void dispatch()
    {
        _is_running = true;
        while(_is_running)
        {
            //_epoller.printFdList();  // for debug
            _LoopOnce();
        }
        _is_running = false;
    }

private:
    // events dispatcher function
    void _LoopOnce()
    {
        int num_events = _epoller.wait(_revs, g_max_events, _timeout_ms);
        for(int i = 0; i < num_events; ++i)
        {
            int fd = _revs[i].data.fd;
            uint32_t events = _revs[i].events;
            
            // turn all problems which occurred on this fd into read/write events
            if(events & EPOLLERR || events & EPOLLHUP)
            {
                events |= EPOLLIN | EPOLLOUT;
            }

            // call the corresponding callback function of event kind (such as EPOLLIN, EPOLLOUT)
            auto conn = _connections[fd];
            if(events & EPOLLIN)
            {
                auto callback = conn->getRecverCallback();
                if(callback)
                {
                    callback(conn);
                }
            }
            if(events & EPOLLOUT)
            {
                auto callback = conn->getSenderCallback();
                if(callback)
                {
                    callback(conn);
                }
            }
        }
    }

private:
    Epoller _epoller;
    struct epoll_event* _revs;  // max size is g_max_events
    std::unordered_map<int, Connection::ptr> _connections;  // fd -> shared_ptr<Connection>
    int _timeout_ms;  // timeout for epoll_wait
    bool _is_running;
};